7-1 高速缓存方案cache-manager
本节目标
了解 NestJS 中基于 cache-manager 的高阶缓存方案,掌握内存缓存与接口缓存的集成方法,理解缓存更新策略,并学会区分不同版本的 TTL 配置差异。
cache-manager 简介
cache-manager 是一个 Node.js 通用缓存管理库,NestJS 官方基于它开发了 @nestjs/cache-manager 模块。它提供了统一的缓存抽象层,支持对接多种 Store 后端:
| Store 类型 | 说明 | 适用场景 |
|---|---|---|
| memory | 内存缓存(内置) | 开发调试、短生命周期数据 |
| ioredis | Redis 客户端 | 生产环境、需要持久化 |
| redis | Redis 客户端(旧版) | 兼容旧项目 |
| lru | LRU 缓存策略 | 有限内存空间的最近使用缓存 |
| fs | 文件系统 | 离线场景 |
| mongodb / mongoose | MongoDB 存储 | 已有 MongoDB 基础设施时 |
内存缓存集成
最简单的集成方式 -- 无需任何参数即可使用内存缓存:
// app.module.ts
import { CacheModule } from '@nestjs/cache-manager';
@Module({
imports: [
CacheModule.register({
ttl: 3000, // 毫秒(v5 版本),v4 版本为秒
}),
],
})
export class AppModule {}
typescript
在 Service 中注入并使用:
import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';
@Injectable()
export class AppService {
constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
async getToken(token?: string) {
// 读取缓存
const cached = await this.cacheManager.get('token');
if (cached) return cached;
// 设置缓存
await this.cacheManager.set('token', token || 'default', 3000);
return token;
}
}
typescript
Cache 实例的核心方法:
| 方法 | 说明 | 返回值 |
|---|---|---|
get<T>(key) | 获取缓存值 | Promise<T> |
set<T>(key, value, ttl?) | 设置缓存值 | Promise<void> |
del(key) | 删除指定缓存 | Promise<void> |
reset() | 清空所有缓存 | Promise<void> |
store | 底层 Store 实例 | BaseStore |
TTL 版本差异
重要:cache-manager v4 和 v5 的 TTL 单位不同,这是调试时最容易踩的坑。
| 版本 | TTL 单位 | 默认值 |
|---|---|---|
| v4 | 秒(seconds) | 5 |
| v5 | 毫秒(milliseconds) | 5000 |
检查方式:
# 查看 package.json 中 cache-manager 的版本
cat node_modules/cache-manager/package.json | grep version
bash
接口级缓存(拦截器方式)
NestJS 提供了 CacheInterceptor,可以自动缓存接口响应:
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';
import { Controller, Get, UseInterceptors } from '@nestjs/common';
@Controller('api/v2')
@UseInterceptors(CacheInterceptor)
export class AppController {
@Get()
@CacheKey('custom_cache_key') // 自定义缓存键
@CacheTTL(5000) // 自定义过期时间(毫秒)
async findAll() {
return await this.service.getData();
}
}
typescript
工作原理:
客户端请求 -> CacheInterceptor
|
v
缓存中是否存在该键?
/ \
是 否
| |
返回缓存数据 执行路由处理函数
|
v
将响应写入缓存
|
v
返回响应数据
text
缓存更新策略
接口缓存的核心问题:当底层数据源更新时,缓存数据可能过时(Stale Cache)。以下是几种常见的缓存更新策略:
策略一:TTL 定时过期
CacheModule.register({
ttl: 30000, // 30秒后自动过期
})
typescript
适用于:配置信息、字典数据等变化频率低的场景。
策略二:客户端主动刷新
通过请求参数中的标志位控制是否使用缓存:
async getData(refresh?: boolean) {
if (!refresh) {
const cached = await this.cacheManager.get('data');
if (cached) return cached;
}
const data = await this.repository.findAll();
await this.cacheManager.set('data', data);
return data;
}
typescript
适用于:用户主动刷新、数据一致性要求较高的场景。
策略三:服务端标志位控制
在写操作(create/update)时更新缓存标志位,读操作时检查标志位:
写入流程:
Controller.update() -> Database.update() -> cacheManager.set('version', timestamp)
读取流程:
Controller.find() -> 检查 version -> 与本地缓存的 version 比较
-> 一致: 返回缓存
-> 不一致: 查询数据库 -> 更新缓存 -> 更新本地 version
text
适用于:对数据实时性有要求但不想每次都查询数据库的场景。
全局缓存配置
// main.ts
import { CacheInterceptor } from '@nestjs/cache-manager';
import { APP_INTERCEPTOR } from '@nestjs/core';
// 方式一:在模块中全局注册
CacheModule.register({
isGlobal: true, // 全局可用
ttl: 5000,
})
typescript
注意:全局缓存拦截器会将所有 GET 请求的响应都缓存下来,一般不推荐在生产环境中全局开启。只对确实需要缓存的接口单独使用拦截器。
适用场景与注意事项
适合使用内存缓存的场景:
- 高频访问、数据量小的配置信息。
- 首页或热门接口的查询结果。
- 密钥计算等中间结果的临时存储。
- 模块间的数据传递。
注意事项:
- 内存缓存在进程重启后会丢失,不能存储敏感数据。
- 不适合存储大量数据,会占用应用内存。
- TTL 过短的缓存效果有限,TTL 过长可能导致数据严重过时。
本节小结
- 理解
cache-manager的多 Store 架构和适用场景。 - 掌握内存缓存的注册、注入和基本操作(get/set/del/reset)。
- 了解 TTL 在 v4(秒)和 v5(毫秒)版本中的单位差异。
- 掌握
CacheInterceptor的接口级缓存用法及@CacheKey、@CacheTTL装饰器。 - 理解三种缓存更新策略的优缺点和适用场景。
↑